#pragma once
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <cstdarg>
#include <ctime>
#include <fstream>
#include <cstring>
#include <mutex>

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    const std::string glogfile = "./log.txt"; // 默认日志文件为当前路径下的log.txt
    std::mutex mtx;
    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LevelTostring(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKONW";
        }
    }

    class logmessage
    {
    public:
        std::string _level;
        pid_t _id;
        std::string _filename;
        int _filenumber;
        std::string _cur_time;
        std::string _message_info;
    };

    std::string GetCurTime()
    {
        time_t now = time(nullptr);
        char buffer[1024];
        struct tm *cur_time = localtime(&now);
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                 cur_time->tm_year + 1900,
                 cur_time->tm_mon + 1,
                 cur_time->tm_mday,
                 cur_time->tm_hour,
                 cur_time->tm_min,
                 cur_time->tm_sec);
        return buffer;
    }
    class Log
    {
    public:
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE) {}

        void Enable(int type) // 用于更换日志打印位置
        {
            _type = type;
        }
        void LogMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage log;
            log._level = LevelTostring(level);
            log._id = getpid();
            log._filename = filename;
            log._filenumber = filenumber;
            log._cur_time = GetCurTime();

            va_list ap; // 用于提取可变参数的数据
            va_start(ap, format);
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap);
            log._message_info = log_info;

            FlushLog(log); // 打印日志
        }
        
        private:
        void FlushToScreen(const logmessage &lm)
        {
            printf("[%s][pid:%d][%s][%d][%s] %s",
                   lm._level.c_str(),
                   lm._id,
                   lm._filename.c_str(),
                   lm._filenumber,
                   lm._cur_time.c_str(),
                   lm._message_info.c_str());
        }

        void FlushToFile(const logmessage &lm)
        {
            std::ofstream fe(_logfile, std::ios::app);
            if (!fe.is_open())
            {
                printf("file open fail!\n");
                return;
            }

            char buffer[2048];
            snprintf(buffer, sizeof(buffer), "[%s][pid:%d][%s][%d][%s] %s",
                     lm._level.c_str(),
                     lm._id,
                     lm._filename.c_str(),
                     lm._filenumber,
                     lm._cur_time.c_str(),
                     lm._message_info.c_str());

            fe.write(buffer, strlen(buffer));
            fe.close();
        }

        void FlushLog(const logmessage &lm)
        {
            std::lock_guard<std::mutex> lkgd(mtx); // 防止多线程信息混乱
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushToScreen(lm);
                break;

            case FILE_TYPE:
                FlushToFile(lm);
                break;
            }
        }

        
    private:
        int _type; // 用于决定打印在屏幕上或某个文件里
        std::string _logfile;
    };

    // 使用宏简化调用
    Log lg;

#define LOG(Level, Format, ...)                                        \
    do                                                                 \
    {                                                                  \
        lg.LogMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
#define EnableFile()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)

